#ifndef _MOUSE_H_
#define _MOUSE_H_

//class for reading PPM file


template <typename Value>
class ViewMatrix 
{
protected :
	typedef Value           ValueType ;
	typedef Vector<Value>   VectorType ;
	typedef Vector<Value>   PointType ;

public:
	ViewMatrix () {}
	ViewMatrix (ViewMatrix & vm)
	{
		for (int i = 0; i < 16; ++i )
		{
			this->modelViewMatrix[i] = vm.modelViewMatrix[i];
			this->projectionMatrix[i] = vm.projectionMatrix[i];
                        
		}
		for (int i = 0; i < 4; ++i )
		{
			this->viewPort [i] = vm.viewPort[i];
		}
	}
	void getCoordEnv()
	{
		glGetDoublev (GL_MODELVIEW_MATRIX, this->modelViewMatrix);
		glGetDoublev (GL_PROJECTION_MATRIX, this->projectionMatrix);
		glGetIntegerv(GL_VIEWPORT, this->viewPort);     
	}
	double modelViewMatrix[16];
	double projectionMatrix[16];  
	int viewPort[4];

	inline VectorType mappingObj2Win (const VectorType & objCoord)
	{
		double winx, winy, winz ;
		gluProject(objCoord.x, objCoord.y, objCoord.z, modelViewMatrix, projectionMatrix, viewPort,
				   & winx, & winy, & winz );
		winy = window_height - winy ;
		return Vector<Value> (float(winx), float(winy), float(winz));
	}

	inline VectorType mappingWin2Obj (const VectorType & winPos)
	{
		double x, y, z;
		gluUnProject(winPos[0], window_height - winPos[1], winPos[2], modelViewMatrix, projectionMatrix, viewPort, 
					 & x, &y , &z);
		return VectorType (ValueType(x), ValueType(y), ValueType(z));
	}

	
	void pick_ray(const VectorType & winPos, PointType & p1, PointType & p2)
	{
		// p1 is on z-near
		// p2 is on z-far
		p1  = mappingWin2Obj(VectorType (winPos[0], winPos[1], 0.0f));
		p2  = mappingWin2Obj(VectorType (winPos[0], winPos[1], 1.0f));
	}


public:
	unsigned window_height ;
	unsigned window_width; 
	
	
	
};

template <typename Value>
class Mouse
{
protected :
	typedef Value           ValueType ;
	typedef Vector<Value>   VectorType ;
	typedef Vector<Value>   PointType ;

public:

    Mouse()
		: size(0.0025), pixel_size(50) , win_pos(VectorType(0, 0,0))
	{ }
	Mouse(const Mouse & mouse)
	{
		this->brushVM = mouse.brushVM ;
		this->objVM = mouse.objVM ;
		this->win_pos = mouse.win_pos ;
		this->size = mouse.size ;
		this->pixel_size = mouse.pixel_size ;
	}

	void set_window_width (unsigned _width)
	{
		this->objectVM.window_width = _width; 
		this->mouseVM.window_width = _width; 
	}

	void set_window_height (unsigned _height)
	{
		this->objectVM.window_height = _height; 
		this->mouseVM.window_height = _height; 
	}
	virtual ~ Mouse() {}
	virtual inline const float get_size() const {return size ;}
	virtual inline void set_size(float size) {this->size = size ;}
	virtual inline const float get_pixel_size() const {return pixel_size ;}
	virtual inline void set_pixel_size(float size) {this->pixel_size = size ;}
	/**
	 * Set the window coordinate of mouse ;
	 * @param  the window coordinate, z-buffer value is ignored; 
	 */
	virtual inline void set_win_pos (const VectorType & pos ) {this->win_pos = pos;}
	/**
	 * Get the window coordinate of mouse ;
	 * @return  the window coordinate, including z-buffer; 
	 */
	virtual inline const Vector<Value> & get_win_pos () const {return this->win_pos; }

	virtual void draw ()
	{
		draw(win_pos);
	}

	/**
	 * Draw mouse on the screen.
	 * @param the window coordinate of mouse ;
	 */
	virtual void draw (Vector<Value> mousePos) //Vector2 mousePos 
	{
		/*
		update_brush_size();
		getBrushCoordEnv();
		Vector3 objPos = brushVM.mappingWin2Obj(Vector3(mousePos.x, mousePos.y, .99f));
			//mappingWin2Obj(Vector3(mousePos.x, mousePos.y, .99f), bModelViewMatrix, bProjectionMatrix, bViewPort);
		//Set the brush transparent
		
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND) ;
		glLineWidth(3) ;
		glBegin(GL_LINE_LOOP) ;
		glColor4f(0.75,0.75,0.75,0.75);
		for (float angle = 0.0f ; angle <= 3.1415926f * 2.0f + 0.1f ; angle += 3.1415926f * 2.0f/ 50.0f)
		{
			glVertex3f(objPos.x + cos(angle) * size,  objPos.y + sin(angle) * size, 0.0f);
		}
		glEnd();
		glLineWidth(1) ;
		glDisable(GL_BLEND);
		*/
	}

	void getObjCoordEnv ()		{	this->objectVM.getCoordEnv();	}
	void getBrushCoordEnv()		{	this->brushVM.getCoordEnv();	}

	//This is check in texcoord;
	bool isInMouse(const VectorType & objCoord)
	{
		float ratio ;
		return isInBrush(objCoord, ratio);
	}
	bool isInMouse(const VectorType & objCoord, float & ratio)
	{
		/*
		Vector3 winCoord = texVM.mappingObj2Win(objCoord) ;
			//mappingObj2Win(objCoord, this->modelViewMatrix, this->projectionMatrix, this->viewPort);
		ratio = 0;
		float distance = sqrt((winCoord.x - tex_coord.x) * (winCoord.x - tex_coord.x)
					+ (winCoord.y - tex_coord.y) * (winCoord.y - tex_coord.y));
		if (  distance <= pixel_size )
		{
			ratio = distance / pixel_size; 
			return true ;
		}
		return false ;
		*/
		return true; 
	}

	/*
	inline bool isVisible(Point point[3])
	{
		Point3 p[3];
		for (int i = 0;i < 3; ++i)
		{
			p[i] = objVM.mappingObj2Win(point[i]); 
		}
		Vector3 v1 = p[1]-p[0];
		Vector3 v2 = p[2]-p[0];
		return  (v1^v2).z < 0;
	}
	*/
	inline VectorType mappingObj2Win (VectorType & objCoord)
	{
		return objectVM.mappingObj2Win(objCoord);
	}

	
	void pick_ray(PointType & p1, PointType & p2)
	{
		objectVM.pick_ray(win_pos, p1, p2);
	}
		

	void get_rect (int & x, int & y, unsigned & w, unsigned & h)
	{
		x = win_pos.x - pixel_size ;
		y = win_pos.y - pixel_size ;
		w = 2 * pixel_size ;
		h = 2 * pixel_size ;
	}


	void update_pixel_size()
	{
		//size_obj2pixel();
	}

	void update_size() 
	{
		//size_pixel2obj();
	}
protected:
	/*
	virtual void size_obj2pixel ()
	{
		unsigned x = WINDOW_WIDTH / 2;
		unsigned y = WINDOW_HEIGHT / 2;
		Vector3 objPos = brushVM.mappingWin2Obj(Vector3 (x, y, 0.99f));
		objPos.x += this->size ;
		Vector3 winPos = brushVM.mappingObj2Win(objPos); 
		this->pixel_size = winPos.x - x; 
	}

	virtual void size_pixel2obj()
	{
		Vector3 p1 = brushVM.mappingWin2Obj(Vector3(win_pos.x, win_pos.y, 0.99f));
		Vector3 p2 = brushVM.mappingWin2Obj(Vector3(win_pos.x + pixel_size, win_pos.y, 0.99f));
		this->size = p2.x - p1.x ;
	}
	*/

public:

	ViewMatrix <float> objectVM ;
	ViewMatrix <float> mouseVM;

protected:
	ValueType size ; //Geometry size
	ValueType pixel_size ; // Brush pixel size on the screen
						// Left and right view have the same pixel_size; 
	VectorType win_pos;

public:
	
};


#endif
